package com.jrx.anytxn.common.uitl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.PropertyValues;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;

/**
 * @program: anytxn-cf-common
 * @description: object map转换功能工具类
 * @author: xiaoyu.jing
 * @date: 2019-10-13 14:27
 **/
public class BeanUtils {

    private static PropertyValuesResolver resolver = new PropertyValuesResolver();

    private BeanUtils() {}

    public static <T> T toObject(Map<String, Object> data, Class<T> clazz) {
        Object object = instantiate(clazz);

        PropertyValues pvs = resolver.resolve(data, clazz);
        DataBinder binder = new DataBinder(object);
        binder.bind(pvs);
        BindingResult result = binder.getBindingResult();

        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
        }
        return (T)binder.getTarget();
    }

    public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
        Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface()) {
            throw new BeanInstantiationException(clazz, "Specified class is an interface");
        }
        try {
            return clazz.newInstance();
        } catch (InstantiationException ex) {
            throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
        } catch (IllegalAccessException ignored) {
        }
        throw new BeanInstantiationException(clazz, "Is the constructor accessible?");
    }

    /**
     * object to map added by jing.xy 2016/10/11
     *
     * @param object
     * @return
     */
    public static Map<String, Object> toMap(Object object) {
        return toMap(object, "class");
    }

    /**
     * object to map added by jing.xy 2016/10/11
     *
     * @param object
     * @param ignoreProperties
     * @return
     */
    public static Map<String, Object> toMap(Object object, String... ignoreProperties) {
        return toMap(object, new HashSet<>(), false, ignoreProperties);
    }

    /**
     * object to map added by jing.xy 2016/10/11
     *
     * @param object
     * @return
     */
    public static Map<String, Object> toFlatMap(Object object) {
        return toFlatMap(object, "class");
    }

    /**
     * object to map added by jing.xy 2016/10/11
     *
     * @param object
     * @param ignoreProperties
     * @return
     */
    public static Map<String, Object> toFlatMap(Object object, String... ignoreProperties) {
        return toMap(object, new HashSet<>(), true, ignoreProperties);
    }

    private static Map<String, Object> toMap(Object object, Set<Object> convertContext, boolean flat,
        String[] ignoreProperties) {
        return toMap(null, object, convertContext, flat, ignoreProperties);
    }

    private static Map<String, Object> toMap(String key, Object object, Set<Object> convertContext, boolean flat,
        String[] ignoreProperties) {
        if (null == object) {
            return null;
        }
        if (convertContext.contains(object)) {
            return null;
        }
        Class<?> type = object.getClass();
        if (isSimpleValueType(type)) {
            throw new RuntimeException("Simple type[" + type.getName() + "] can not to map!");
        }
        convertContext.add(object);
        Map<String, Object> desc = new HashMap<>();
        PropertyDescriptor[] pds = org.springframework.beans.BeanUtils.getPropertyDescriptors(type);
        List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
        for (PropertyDescriptor pd : pds) {
            if ((null == ignoreProperties) || (!ignoreList.contains(pd.getName()))) {
                Method readMethod = pd.getReadMethod();
                if (readMethod != null) {
                    if (!readMethod.isAccessible()) {
                        readMethod.setAccessible(true);
                    }
                    desc.put(pd.getName(), ReflectionUtils.invokeMethod(readMethod, object));
                }
            }
        }
        Map<String, Object> resultMap = new HashMap<>();
        for (String innerKey : desc.keySet()) {
            Object value = desc.get(innerKey);
            if (flat) {
                String subKey = key + "." + innerKey;
                Map<String, Object> flatMap = descFlat(subKey, value, convertContext, ignoreProperties);
                if (null != flatMap) {
                    resultMap.putAll(flatMap);
                }
            } else {
                resultMap.put(innerKey, desc(value, convertContext, ignoreProperties));
            }
        }
        return resultMap;
    }

    /**
     * check SimpleValueTypeClass added by jing.xy
     *
     * @param clazz
     * @return
     */
    public static boolean isSimpleValueType(Class<?> clazz) {
        return (ClassUtils.isPrimitiveOrWrapper(clazz)) || (clazz.isEnum())
            || (CharSequence.class.isAssignableFrom(clazz)) || (Number.class.isAssignableFrom(clazz))
            || (Date.class.isAssignableFrom(clazz)) || (clazz.equals(URI.class)) || (clazz.equals(URL.class))
            || (clazz.equals(Locale.class)) || (clazz.equals(Class.class));
    }

    private static Object desc(Object object, Set<Object> context, String[] ignoreProperties) {
        if ((null == object) || (context.contains(object))) {
            return null;
        }
        Class<?> type = object.getClass();
        if ((isSimpleValueType(type)) || (type.isArray())) {
            return object;
        }
        if ((object instanceof Collection)) {
            @SuppressWarnings("unchecked")
            Collection<Object> coll = (Collection<Object>)object;
            List<Object> resultList = new ArrayList<>(coll.size());
            for (Object o : coll) {
                Object value = desc(o, context, ignoreProperties);
                resultList.add(value);
            }
            return resultList;
        }
        if ((object instanceof Map)) {
            Map<?, ?> mapValue = (Map<?, ?>)object;
            Map<String, Object> resultMap = new HashMap<>();
            for (Object key : mapValue.keySet()) {
                Object value = mapValue.get(key);
                resultMap.put(key.toString(), desc(value, context, ignoreProperties));
            }
            return resultMap;
        }
        return toMap(object, context, false, ignoreProperties);
    }

    private static Map<String, Object> descFlat(String key, Object object, Set<Object> context,
        String[] ignoreProperties) {
        Map<String, Object> result = new HashMap<>();
        if ((null == object) || (context.contains(object))) {
            result.put(key, null);
            return result;
        }
        Class<?> type = object.getClass();
        if ((isSimpleValueType(type)) || (type.isArray())) {
            result.put(key, object);
            return result;
        }
        if ((object instanceof Collection)) {
            @SuppressWarnings("unchecked")
            Collection<Object> coll = (Collection<Object>)object;
            List<Object> resultList = new ArrayList<>(coll.size());
            for (Object o : coll) {
                Object value = descFlat(null, o, context, ignoreProperties);
                resultList.add(value);
            }
            result.put(key, resultList);
            return result;
        }
        if ((object instanceof Map)) {
            Map<?, ?> mapValue = (Map<?, ?>)object;
            Map<String, Object> resultMap = new HashMap<>();
            for (Object innerKey : mapValue.keySet()) {
                Object value = mapValue.get(innerKey);
                String nextKey = key + "." + innerKey;

                Map<String, Object> nestedDesc = descFlat(nextKey, value, context, ignoreProperties);
                resultMap.putAll(nestedDesc);
            }
            return resultMap;
        }
        return toMap(key, object, context, true, ignoreProperties);
    }

    /**
     * 拆分list
     * 
     * @param list
     * @param groupSize
     * @param <T>
     * @return
     */
    public static <T> List<List<T>> splitList(List<T> list, int groupSize) {
        int length = list.size();
        // 计算可以分成多少组
        int num = (length + groupSize - 1) / groupSize; // TODO
        List<List<T>> newList = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            // 开始位置
            int fromIndex = i * groupSize;
            // 结束位置
            int toIndex = (i + 1) * groupSize < length ? (i + 1) * groupSize : length;
            newList.add(list.subList(fromIndex, toIndex));
        }
        return newList;
    }

    /**
     * 复制 list 对象
     * @param source
     * @param target
     * @param targetClassObj
     */
    public static void copyList(List source, List target, Class targetClassObj) {
        source.forEach(item -> {
            try {
                Object data = targetClassObj.newInstance();
                org.springframework.beans.BeanUtils.copyProperties(item, data);
                target.add(data);
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            }
        });
    }

    public static boolean checkForNull(Object obj) {
        if (null == obj) {
            return true;
        }

        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object o = field.get(obj);
                if (null == o) {
                    return true;
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return true;
            }
        }

        return false;
    }
}
